React Compiler 的運作流程很複雜,概括來說會執行以下幾個步驟:
抽象語法樹 (AST) 是一種樹狀結構,用來表示程式碼的語法結構。如果好奇怎麼呈現的可以使用 AST Explorer 來觀察。
首先先介紹 控制流圖 (Control-Flow Graph,CFG),控制流圖是一種圖形表示法,用來表示程式在執行過程中可能走訪的所有路徑。
控制流圖能幫助編譯器理解程式的執行路徑,而將抽象語法樹(AST)轉換成 HIR 的過程稱為 Lowering,而 HIR 是一種呈現控制流圖的方式,呈現方式會是用一個個區塊 (block) 組成。
SSA 是一種表示方式,確保每個變數在程式中只被賦值一次,可以讓編譯器能更容易進行優化。
簡易範例說明:
let a = 5;
let b = a + 2;
在 SSA 形式下,可能會被表示成這樣:
a1 = 5
b1 = a1 + 2
a1 和 b1 會是唯一的變數名稱,
常數傳播是一種優化技術,將變數替換為已知的常數值來提高程式的執行效率。
這是一個簡單的範例:
function calculateTotal(price: number) {
  const taxRate = 0.1; // 這是一個常數
  const total = price + price * taxRate;
  return total;
}
會轉換成
function calculateTotal(price: number) {
  const total = price + price * 0.1; // 直接使用常數
  return total;
}
假設有一個簡單 Component
export default function MyApp() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Add</button>
    </div>
  );
}
透過 React Compiler 最後會被轉換成這樣
function MyApp() {
  const $ = _c(2);
  const [count, setCount] = useState(0);
  let t0;
  // 檢查快取的第一個項目是否與 count 相同
  if ($[0] !== count) {
    t0 = (
      <div>
        <p>{count}</p>
        <button onClick={() => setCount(count + 1)}>Add</button>
      </div>
    );
    // 更新快取的第一個項目為當前的 count
    $[0] = count;
    // 更新快取的第二個項目為當前渲染的內容
    $[1] = t0;
  } else {
    // 如果相同,則直接使用快取的渲染結果
    t0 = $[1];
  }
  return t0;
}
在裡面有一個函數是 _c,實際上使用的是 useMemoCache 的函數。useMemoCache 的函數是 React Compiler 內部使用的,用來管理 memoization 所用的快取。
更詳細的細節可以在 React Playground 進行測試,查看每一個流程的結果。
參考資料:
https://github.com/facebook/react/blob/main/compiler/docs/DESIGN_GOALS.md#architecture
https://yongseok.me/blog/en/react_compiler_1/
https://yongseok.me/blog/en/react_compiler_2/
https://yongseok.me/blog/en/react_compiler_3/
https://yongseok.me/blog/en/react_compiler_4/
https://www.youtube.com/watch?v=PYHBHK37xlE
https://www.youtube.com/watch?v=uA_PVyZP7AI
https://en.wikipedia.org/wiki/Control-flow_graph
https://zh.wikipedia.org/zh-tw/%E9%9D%99%E6%80%81%E5%8D%95%E8%B5%8B%E5%80%BC%E5%BD%A2%E5%BC%8F